/**
 * MINAPL Code generator.
 * Copyright 2012 Michael A. Muller
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */


#ifndef _minapl_CodeGen_h_
#define _minapl_CodeGen_h_

#include <stdint.h>
#include <stdlib.h>
#include "Array.h"

namespace minapl {

enum HintType {
    HINT_ADD,
    HINT_SUB,
    HINT_MUL,
    HINT_DIV,
    HINT_EQ,
    HINT_CALL
};

// The code generator.
struct CodeGen {

    /**
    * codeBuffer - The buffer where the output code is written.  curCode is a
    * pointer into the current write position in that buffer.  codeSize is, of
    * course, the allocated size.
    */
    uint8_t *codeBuffer, *curCode;
    size_t codeSize;

    // Keyed data.  this allows ASTNode.emit() methods to communicate data
    // between different levels of the node tree.
    struct KeyVal {
        void *key, *val;
        KeyVal() : key(0), val(0) {}
        KeyVal(void *key, void *val) : key(key), val(val) {}
    };

    Array<KeyVal> keyedData;

    CodeGen();

    ~CodeGen();

    void setKey(void *key, void *val);

    void *getKey(void *key);

    void deleteKey(void *key);

    /**
    * Tell the buffer that we need to write numBytes new bytes to it and that it
    * should reallocate if necessary.
    */
    void need(int numBytes);

    void emitByte(uint8_t byte) {
        need(1);
        *curCode++ = byte;
    }

    void emitWord(void *word) {
        need(sizeof(word));
        *(void **)curCode = word;
        curCode += sizeof(word);
    }

    void emitOffset(void *addr) {
        emitWord( (void *)( (uint8_t *)addr - (curCode + 4) ) );
    }

    /**
     * Allocate global memory.
     */
    void *allocGlobal(size_t size) {
        return malloc(size);
    }

    void loadAddr(void *addr);

    void loadZero();

    void pushLast();

    void emitFuncCall(void *addr);

    void emitFuncEnter();

    void emitReturn();

    void emitLocalVarRef(int index);

    void emitLocalVarStore(int index);

    /**
     * Emit an instruction that loads the effective address of a local
     * variable.
     */
    void emitLoadLocalVarAddr(int index);

    /**
     * Emit an instruction to add the specified number of bytes to the stack
     * pointer address.  This can be used to clean up the stack or to allocate
     * more space on it.  Keep in mind that these are BYTES.  Multiply by
     * sizeof(void *) to deal with words.
     * If amt == 0, does not emit an instruction.
     */
    void emitSPAdd(ssize_t amt);

    /** Emit instruction to load the stack pointer. */
    void emitLoadSP();

    /**
     * Emit an intruction to store the accumulator as an argument for the
     * operation provided by 'hint' (see the HINT_* constants above).
     */
    void emitStoreArg(int index, int hint);

    /**
     * Emit a test for equality on accum, op2.  This will store 1 in the
     * accumulator if the operands are equal, zero if not.
     */
    void emitEqTest();

    /**
     * Emit a test for "not equals."
     */
    void emitNEqTest();

    /**
     * Emit "logical not" instruction.
     */
    void emitNot();

    void emitSub();

    void emitAdd();

    void emitMul();

    void emitDiv();


    /**
     * Emit a jump to the specified address.
     */
    void emitJump(void *addr);

    /**
     * Emit a jump to an incomplete address.  Returns a pointer that can be
     * used to fix the instruction (see fixAddr()) once the address is known.
     */
    void **emitIncompleteJump();

    /**
     * Emit a "jump if zero" to an incomplete address.  See
     * emitIncompleteJump(), same rules apply.
     */
    void **emitIncompleteJZ();

    /**
     * Emit a call to an incomplete address.  See emitIncompleteJump(), same
     * rules apply.
     */
    void **emitIncompleteCall();

    /**
     * Fix a call site address returned by emitIncompleteJump() or
     * emitIncompleteCall().
     */
    void fixAddr(void **callSite, void *addr);

    /**
     * Load the word at the address referenced by the accumulator into the
     * accumulator. ("p = *p" in C)
     */
    void emitLoad();

};

} // namespace minapl

#endif
